Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First cut of the Transformation Playground Frontend and Backend #1190

Open
wants to merge 20 commits into
base: main
Choose a base branch
from

Conversation

chelma
Copy link
Member

@chelma chelma commented Dec 10, 2024

Description

  • Added an initial cut of the Transformation Playground backend
  • The user is able to start up the Django webserver and POST against the transforms/index/ path to create some Python code that will transform an input ES 6.8 Index Settings w/ multi-type mappings or ES 7.10 Index Settings into equivalent OpenSearch 2.X settings. The API returns the transform code, the result of invoking the transform against the user's input, and the validation results.
  • The backend takes the user's input JSON, runs it through Claude 3.5 Sonnet to generate some Python transformation code, loads the transform code, invokes the transform against the input, and creates/deletes each transformed Index setting against the OpenSearch cluster the user provided for testing.
  • The user is also able to spin up a quick React/Cloudscape frontend and hit the backend in their web browser. The frontend takes in the user's input JSON and request a GenAI recommendation. They can clear their current transform as well. There's dropdowns for different configuration options, though currently the only one with different options is the Source, which supports both ES 6.8 and ES 7.10. The user is able to set static guidance for the GenAI recommendation to shape its output, as well as directly modify and test their own transformation code without involving GenAI.

Issues Resolved

Testing

  • Added unit tests
  • Ran manual tests. Example:
(venv) chelma@80a9970a4d02 tp_backend % python3 manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
December 10, 2024 - 19:14:58
Django version 5.1.4, using settings 'tp_backend.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
(venv) chelma@80a9970a4d02 LP04 % curl -X POST "http://127.0.0.1:8000/transforms/index/" -H "Content-Type: application/json" -d '
{
    "transform_language": "Python",
    "source_version": "Elasticsearch 6.8",
    "target_version": "OpenSearch 2.17",
    "input_shape": {
        "index_name": "test-index",
        "index_json": {
            "settings": {
                "index": {
                    "number_of_shards": 1,
                    "number_of_replicas": 0
                }
            },
            "mappings": {
                "type1": {
                    "properties": {
                        "title": { "type": "text" }
                    }
                },
                "type2": {
                    "properties": {
                        "contents": { "type": "text" }
                    }
                }
            }
        }
    },
    "test_target_url": "http://localhost:29200"
}'

{
    "output_shape": [
        {
            "index_name": "test-index-type1",
            "index_json": {
                "settings": {
                    "index": {
                        "number_of_shards": 1,
                        "number_of_replicas": 0
                    }
                },
                "mappings": {
                    "properties": {
                        "title": {
                            "type": "text"
                        }
                    }
                }
            }
        },
        {
            "index_name": "test-index-type2",
            "index_json": {
                "settings": {
                    "index": {
                        "number_of_shards": 1,
                        "number_of_replicas": 0
                    }
                },
                "mappings": {
                    "properties": {
                        "contents": {
                            "type": "text"
                        }
                    }
                }
            }
        }
    ],
    "transform_logic": "from typing import Dict, Any, List\nimport copy\n\n\"\"\"\nThis transformation function converts Elasticsearch 6.8 index settings to OpenSearch 2.17 compatible format.\nIt handles the removal of type-based mappings by creating separate indexes for each type.\n\"\"\"\n\ndef transform(source_json: Dict[str, Any]) -> List[Dict[str, Any]]:\n    result = []\n    index_name = source_json['index_name']\n    index_json = source_json['index_json']\n    \n    # Extract settings\n    settings = index_json.get('settings', {})\n    \n    # Extract mappings\n    mappings = index_json.get('mappings', {})\n    \n    # Create a separate index for each type\n    for type_name, type_mapping in mappings.items():\n        new_index_name = f\"{index_name}-{type_name}\"\n        new_index_json = {\n            'settings': copy.deepcopy(settings),\n            'mappings': type_mapping\n        }\n        \n        result.append({\n            'index_name': new_index_name,\n            'index_json': new_index_json\n        })\n    \n    return result",
    "validation_report": [
        "Attempting to load the transform function...",
        "Loaded the transform function without exceptions",
        "Attempting to invoke the transform function against the input...",
        "Invoked the transform function without exceptions",
        "The transformed output has 2 Index entries.",
        "Using target cluster for testing: http://localhost:29200",
        "Attempting to create & delete index 'test-index-type1' with transformed settings...",
        "Created index 'test-index-type1'.  Response: \n{\"acknowledged\": true, \"shards_acknowledged\": true, \"index\": \"test-index-type1\"}",
        "Deleted index 'test-index-type1'.  Response: \n{\"acknowledged\": true}",
        "Attempting to create & delete index 'test-index-type2' with transformed settings...",
        "Created index 'test-index-type2'.  Response: \n{\"acknowledged\": true, \"shards_acknowledged\": true, \"index\": \"test-index-type2\"}",
        "Deleted index 'test-index-type2'.  Response: \n{\"acknowledged\": true}"
    ],
    "validation_outcome": "PASSED"
}

The default view when you open the GUI, with an input ES 6.8 JSON filled in
Screenshot 2025-01-02 at 7 22 09 AM

The GenAI recommended transformation for that input JSON
Screenshot 2025-01-02 at 7 22 42 AM

The modal window for modifying the GenAI recommendations
Screenshot 2025-01-02 at 7 27 27 AM

The results of applying the user guidance in a new, GenAI recommend transform
Screenshot 2025-01-02 at 7 28 03 AM

Check List

  • New functionality includes testing
    • All tests pass, including unit test, integration test and doctest
  • New functionality has been documented
  • Commits are signed per the DCO using --signoff

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

@chelma chelma changed the title First cut of the Transformation Playground backend First cut of the Transformation Playground Frontend and Backend Dec 12, 2024
- Do not attempt to be friendly in your responses. Be as direct and succint as possible.
- Think through the problem, extract all data from the task and the previous conversations before creating a plan.
- Never assume any parameter values while invoking a tool or function.
- You may ask clarifying questions to the user if you need more information.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oof, definitely don't this line in here. Remove.

Copy link
Member

@peternied peternied left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for getting this in front of the team @chelma

I did not get a chance to refine the associated jira's in advance of this change arriving - I expect there is going to be back and forth on a number of comments I've raised.

.gitignore Outdated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While there is a readme about this project, it doesn't have an architecture document, lets build one out similar to how we have with RFS but (with scope adjusted).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good callout. I have a bunch of existing docs that can be translated into this format.

Comment on lines +85 to +86
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What goes in this db?

Copy link
Member Author

@chelma chelma Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. In the future, I expect we'll store:

  • The user-loaded inputs from the left panel (what if you have a snapshot or source cluster w/ 10k+ indices? Probably should be stored client-side), which we'll then display portions of in the client.
  • The transformation logic for each "input shape" that the user has made manual changes to, along with the full validation history and the LLM conversation. This will enable us to deploy all the transformations in a bundle that replay/backfill can use, it will facilitate our (the team's) debugging efforts, facilitate a trackable history of what the LLM's actions were and how they were incorporated into the Cx migration (think for security owners), and it will facilitate LLM Model selection/evaluation as well as LLM Fine-Tuning (we could have a collection of real migrations to tune with).
  • It may also store the validation test logic for each input shape. Imagine if, as part of an assessment process, we have an LLM propose additional tests we should be running for validation and then dynamically create and incorporate them. This would be really useful for validating behavior (does the target behave the way we want), not just API spec conformance (does it return a 2XX).

Comment on lines +151 to +162
'transform_api_debug_file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': 'logs/transform_api.debug.log',
'formatter': 'verbose',
},
'transform_api_info_file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': 'logs/transform_api.info.log',
'formatter': 'verbose',
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double checking, does the debug log include the info level logs too? Is this in alignment with the logging working group session we ran?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it includes both sets of logs. I don't remember the working group session, is there a link to its action items/artifacts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding tests, lets make sure to update the CI.yml to run these tests so we have code coverage information as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will investigate!

llm = ChatBedrockConverse(
model="anthropic.claude-3-5-sonnet-20240620-v1:0", # This is the older version of the model, could be updated
temperature=0, # Suitable for straightforward, practical code generation
max_tokens=4096,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How many tokens will we need? Can you frame how we choose this target?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, I expect this to be fully configurable by the user - what if they want to use their own LLM, for example, rather than bedrock? We'll need to figure out what the right level of configurability is for initial user feedback. On the token front - that is the maximum number of output tokens, not input. 4k tokens is a LOT of code, I would be highly surprised if we ever had a transform that needed anywhere close to that. For reference, the ~450 lines of text in the OS 2.7 "knowledge" file is only ~3500 tokens.

</Container>

{/* Testing/Output Column */}
<Container header="Testing & Output Panel">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you cycle back and forth between source / transformation logic / output?

Copy link
Member Author

@chelma chelma Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't implemented this all yet (obviously), but here's the user journey I'm envisioning:

  • The user loads in their input shapes. Currently, that's just copy/pasting JSON into a text field, but soon I'd like to be able to load the contents of a snapshot into the Playground via a configurable process. Imagine a dialogue where you select the S3 location of your snapshot, something extracts the templates, indices, and a configurable number of documents (that's a whole workstream right there), and presents them in the left panel so you can see everything in your snapshot, then click on specific items to see their JSON, etc. Even once we've added the ability to read from snapshots, we'll want to leave open the possibility of adding manual input shapes. Example - the user doesn't have access to a snapshot of their production cluster but they do have access to the settings JSON and a few documents, which they can manually enter as shapes into the Playground for testing.
  • The user then uses the dropdowns to select what type of transformation they are performing. The "transform type" (Index vs. template vs. documents) dropdown will disappear, because it will be obvious from context (the user selected an index on the input panel, so...).
  • Once that is selected, we will have a pre-canned transform (we'll hand-craft and save, no GenAI) that is supplied based on those selections that will serve as a default. The default will be populated into the UI and pinned against that shape in the backend.
  • The user can then hit a "test" button to see how that default transform will work against their input shape (e.g. we kick off the validation process).
  • If the user wants to include functional tests into validation (e.g. actually creating/deleting indices against a real test cluster), they can go through a dialogue to set up and test a connection to their target cluster. Currently, that process is just "paste a URL into a text field and assume there's no auth to worry about").
  • If the user likes the results of the default, pre-canned transform and it passes validation - great! No further work needed. If not, they can either modify the transform directly in the UI and run the validation process again, or they can ask the GenAI assistant to modify the existing transform in some way (not currently implemented, but expect to be easy). Either way, the new transform is then pinned against the input shape in the backend so that we know there's something custom going on.
  • The user then selects a different input shape and goes through this process until everything is transforming as they desire, then they can either hit a button to "deploy" their transformed data/metadata to the target cluster (think for metadata migrations which are inherently "low scale") or "bundle" the transformations for inclusion into the Migrations Assistant backfill/replay processes (which are anything but "low scale"). For the "deploy" button, this is basically just skipping the cleanup step of the validation process we've already performed (e.g. take the transformed output and PUT it against the target, but don't delete it afterwards). For bundling, this is taking the stuff we have in the server-side DB, packaging it into a tarball/zip/whatever, and sticking it somewhere (S3?) so that the backfill and replayer processes can pick it up and load the transform objects.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like 3 different experience, sourcing input json, transformation editing, and viewing output json. Seems like these should be decoupled into different pages, but I'm not sure of the overall workflow.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a lot of benefit in visualizing the three things together; I outlined the user-journey I'm imagining above here. Curious what you think after reading that.

[1] #1190 (comment)

</Container>

{/* Transformation Column */}
<Container header="Transformation Panel">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it should be refactored into its own control to reduce overall coupling

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would love more details! But yeah - this whole page is ripe for refactoring.

Comment on lines 17 to 20
const sourceVersionOptions: SelectProps.Options = [{ label: "Elasticsearch 6.8", value: "Elasticsearch 6.8" }];
const targetVersionOptions: SelectProps.Options = [{ label: "OpenSearch 2.17", value: "OpenSearch 2.17" }];
const transformTypeOptions: SelectProps.Options = [{ label: "Index", value: "Index" }];
const transformLanguageOptions: SelectProps.Options = [{ label: "Python", value: "Python" }];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this data should come from the API, or there should be a mapping of API data against the strings for visualization

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1. I think this comes down to a shared spec between the frontend and backend; this also applies to the shape of the requests/responses that are going over the wire. I know we've briefly discussed OpenAPI or something as a model format.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets use the existing jdango api server in console_api

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a lot of benefit in separating this as a distinct project:

  • The Playground is conceptually only loosely coupled to the rest of the Migration Assistant. There's no reason the snapshot it's reading inputs from needs to come from a console snapshot create command. Similarly, there's no reason the target cluster used for validation (or we do low-scale deployment against) needs to have been created as a part of the Migration Assistant setup process. The best argument might be around the format of the transformation bundle being coupled to the Migration Assistant, but there's no reason we couldn't specify that format to be generic. That said - you don't need to care about the rest of Migration Assistant at all for the Playground to be a useful way of creating/testing these transformations.
  • The Playground is a very different experience than the general operator workflow. It's inherently interactive and cyclical in a way that running basic configuration commands is not.
  • The Playground is generically useful and extensible as a standalone product feature. At it's core, it's a way to visualize data/metadata, have a GenAI assisted process for modifying/transforming it, testing the modifications/transformations, and some mechanisms for deployment. Swapping out what specifically it's using as an input or testing against or has a desired output is inherently modular. There's already multiple other use-cases we've discussed for where this could apply in other domains outside of Elasticsearch to OpenSearch migrations.

The key thing in my mind is that users of the Migration Assistant should have a cohesive overall experience; but I think tightly coupling the Playground to the Migration Assistant Operator API/GUI in a way that prevents re-use would be a mistake.

logger.debug(f"Validation report entries:\n{validation_report.report_entries}")
except TestTargetInnaccessibleError as e:
logger.error(f"Target cluster is not accessible: {str(e)}")
return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.
except Exception as e:
logger.error(f"Testing process failed: {str(e)}")
logger.exception(e)
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants